package cn.cybozu.common;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class JsonNode
{
	private Map<String, JsonNode> map = null; //new HashMap<String, JsonNode>();
	private List<JsonNode> list       = null; //new ArrayList<JsonNode>();
	private Object value      = null;
	private int position = 0;
	private char[] charArray = null;
	
	public JsonNode()
	{
	}
	public JsonNode(Object value)
	{
		this.value = value;
	}
	public JsonNode(List<JsonNode> list)
	{
		this.list = list;
	}
	public void parseJsonNode(String srcJson)
	{
		position = 0;
		charArray = srcJson.toCharArray();
		
		if(isRead())
		{
			char c1 = read();
			if (c1 == '[')
			{
				list = parseArray();
			}
			else if(c1 == '{')
			{
				map  = parseMap();
			}
		}
	}
	public static JsonNode parse(String srcJson)
	{
		JsonNode node = new JsonNode();
		node.parseJsonNode(srcJson);
		return node;
	}
	public JsonNode get(String key)
	{
		if(map != null && map.containsKey(key))
		{
			return map.get(key);
		}
		return null;
	}
	public List<JsonNode> getArray()
	{
		if(list == null)
		{
			return new ArrayList<JsonNode>();
		}
		return list;
	}
	public Map<String, JsonNode> getMap()
	{
		if(map == null)
		{
			return new HashMap<String, JsonNode>();
		}
		return map;
	}
	public String getTextValue()
	{
		if(isNull())
		{
			return null;
		}
		return value.toString();
	}
	public Long getLongValue()
	{
		if(isNull())
		{
			return null;
		}
		return new Long(value.toString());
	}
	public boolean isNull()
	{
		return (map == null || map.size() == 0) && (list == null || list.size() == 0) && value == null;
	}
	public void setJsonNode(String key, JsonNode value)
	{
		if(map == null)
		{
			map = new HashMap<String, JsonNode>();
		}
		map.put(key, value);
	}
	public void removeJsonNode(String key)
	{
		if(map != null && map.containsKey(key))
		{
			map.remove(key);
		}
	}
	public void setValue(Object value)
	{
		this.value = value;
	}
	public void setArray(List<JsonNode> list)
	{
		this.list = list;
	}
	private List<JsonNode> parseArray()
	{
		List<JsonNode> sb = new ArrayList<JsonNode>();

		while(isRead())
		{
			char c1 = read();
			if (c1 == ']')
			{
				break;
			}
			else if (c1 == ' ' || c1 == '\n' || c1 == '\r' || c1 == '\t' || c1 == ',')
			{
				continue;
			}
			sb.add(parseValue(c1));
		}
		return sb;
	}
	
	private Map<String, JsonNode> parseMap()
    {
		Map<String, JsonNode> jsonMap = new HashMap<String, JsonNode>();
		String key = null;
		while(isRead())
		{
			char c1 = read();
			if (c1 == '}')
			{
				break;
			}
			else if (c1 == ' ' || c1 == '\n' || c1 == '\r' || c1 == '\t' || c1 == ',' || c1 == ':')
			{
				continue;
			}
			else if (key == null)
			{
				key = parseString();
				continue;
			}
			JsonNode valueNode = parseValue(c1);
			jsonMap.put(key, valueNode);
			key = null;
		}
		return jsonMap;
	}
	private JsonNode parseValue(char srcChar)
	{
		JsonNode json = new JsonNode();
		switch (srcChar)
		{
			case '[':
				json.list = parseArray();
				return json;
			case '{':
				json.map = parseMap();
				return json;
			case '"':
				json.value = parseString();
				return json;
			case 't':	// true
				position += 3;
				json.value = Boolean.TRUE;
				return json;
			case 'f':	// false
				position += 4;
				json.value = Boolean.FALSE;
				return json;
			case 'n':	// null
				position += 3;
				return json;
			case '0':
			case '1':
			case '2':
			case '3':
			case '4':
			case '5':
			case '6':
			case '7':
			case '8':
			case '9':
			case '-':
				json.value = parseNumber();
				return json;
        }
		// error
        return null;
    }
	public char read()
	{
		char c = charArray[position];
		position++;
		return c;
	}
	
	private boolean isRead()
	{
		return position < charArray.length;
	}
	
	private String parseString()
	{
		StringBuilder sb = new StringBuilder();
		
		while (isRead())
		{
			char c1 = read();
			if(c1 == '\\')
			{
				//sb.append(c1);
				sb.append(read());
			}
			else if (c1 == '"')
			{
				break;
			}
			else
			{
				sb.append(c1);
			}
		}
		return sb.toString();
	}
	
	private String parseNumber()
	{
		position--;
        
		StringBuilder sb = new StringBuilder();
		while (isRead())
		{
			char c1 = read();
			if(c1 == ',' || c1 == ':' || c1 == '}' || c1 == ']')
			{
				position--;
				break;
			}
			sb.append(c1);
		}
		return sb.toString();
	}
	
	public String toString()
	{
		StringBuilder sb = new StringBuilder();
		int count = 0;
		
		if(map != null)
		{
			sb.append("{");
			for(String key : map.keySet())
			{
				if(count != 0)
				{
					sb.append(",");
				}
				sb.append("\"" + key + "\":" + map.get(key).toString());
				count++;
			}
			sb.append("}");
		}
		else if(list != null)
		{

			sb.append("[");
			for(JsonNode json : list)
			{
				if(count != 0)
				{
					sb.append(",");
				}
				sb.append(json.toString());
				count++;
			}
			
			sb.append("]");
			
		}
		else if(value instanceof JsonNode)
		{
			sb.append(value.toString());
		}
		else if(value instanceof String)
		{
			sb.append("\"");
			char[] chars = value.toString().toCharArray();
			for(int i = 0; i<chars.length; i++)
			{
				char c1 = chars[i];
				switch (c1)
				{
					case '"':
					case '\\':
                        sb.append("\\" + c1);
                        break;
					case '\b':
                        sb.append("\\b");
                        break;
					case '\f':
                        sb.append("\\f");
                        break;
                    case '\n':
                        sb.append("\\n");
                        break;
                    case '\r':
                    	sb.append("\\r");
                    case '\t':
                        sb.append("\\t");
                        break;
                    default:
                    	sb.append(c1);
                        break;
                }
            }
            sb.append("\"");
		}
		else if(value != null)
		{
			sb.append("\"" + value.toString()  +"\"");
		}
		else if(value == null)
		{
			sb.append("null");
		}
		return sb.toString();
	}
}
